package org.jetbrains.kotlinconf.ui.components.zoomable.internal

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.layout.ScaleFactor
import androidx.compose.ui.layout.times
import androidx.compose.ui.unit.IntSize
import org.jetbrains.kotlinconf.ui.components.zoomable.BaseZoomFactor
import org.jetbrains.kotlinconf.ui.components.zoomable.ContentZoomFactor
import org.jetbrains.kotlinconf.ui.components.zoomable.UserZoomFactor
import kotlin.math.roundToInt

internal fun Size.roundToIntSize() =
  IntSize(width.roundToInt(), height.roundToInt())

internal fun Size.discardFractionalParts(): IntSize {
  return IntSize(width = width.toInt(), height = height.toInt())
}

internal val ScaleFactor.maxScale: Float
  get() = maxOf(scaleX, scaleY)

internal val ScaleFactor.minScale: Float
  get() = minOf(scaleX, scaleY)

internal operator fun ScaleFactor.unaryMinus(): ScaleFactor =
  this * -1f

internal val ScaleFactor.Companion.Zero
  get() = ScaleFactor(0f, 0f)

internal fun ScaleFactor.isPositiveAndFinite(): Boolean {
  return scaleX.isPositiveAndFinite() && scaleY.isPositiveAndFinite()
}

internal fun Float.isPositiveAndFinite(): Boolean {
  return this.isFinite() && this >= 0f
}

internal val TransformOrigin.Companion.Zero
  get() = TransformOrigin(0f, 0f)

internal operator fun Offset.times(factor: ScaleFactor) =
  Offset(x = x * factor.scaleX, y = y * factor.scaleY)

internal operator fun Offset.div(factor: ScaleFactor) =
  Offset(x = x / factor.scaleX, y = y / factor.scaleY)

internal operator fun Offset.div(zoom: ContentZoomFactor): Offset =
  div(zoom.finalZoom())

internal operator fun Offset.times(zoom: ContentZoomFactor): Offset =
  times(zoom.finalZoom())

internal operator fun Size.times(zoom: ContentZoomFactor): Size =
  times(zoom.finalZoom())

internal operator fun UserZoomFactor.times(operand: Float): UserZoomFactor =
  UserZoomFactor(value.times(operand))

internal operator fun BaseZoomFactor.times(factor: UserZoomFactor): ScaleFactor =
  value.times(factor.value)

internal operator fun UserZoomFactor.minus(other: UserZoomFactor): UserZoomFactor {
  return UserZoomFactor(value = value - other.value)
}

internal operator fun UserZoomFactor.div(other: UserZoomFactor): UserZoomFactor {
  return UserZoomFactor(value = value / other.value)
}

internal fun UserZoomFactor.coerceIn(minimumValue: UserZoomFactor, maximumValue: UserZoomFactor): UserZoomFactor {
  return UserZoomFactor(value = value.coerceIn(minimumValue.value, maximumValue.value))
}

/**
 * Call [action] with [zoom] and [translate] applied to this offset. The value
 * generated by [action] is returned by applying the inverse of [translate] of [zoom].
 *
 * The name of this function was inspired from [DrawScope.withTransform].
 */
internal fun Offset.withZoomAndTranslate(
  zoom: ScaleFactor,
  translate: Offset,
  action: (Offset) -> Offset,
): Offset {
  return (action((this * zoom) + translate) - translate) / zoom
}

internal fun Rect.times(scale: ScaleFactor): Rect =
  Rect(offset = topLeft * scale, size = size * scale)

internal fun Rect.withOrigin(origin: TransformOrigin, action: Rect.() -> Rect): Rect {
  val pivot = Offset(
    x = size.width * origin.pivotFractionX,
    y = size.height * origin.pivotFractionY,
  )
  val atOrigin = this.translate(-pivot)
  val newRect = atOrigin.action()
  return newRect.translate(pivot)
}
